
#include <windows.h>
#include <windowsx.h>
#include <CommCtrl.h>
#include "msgpack.h"
#include "wintab.h"
#define PACKETDATA	(PK_X | PK_Y | PK_BUTTONS | PK_NORMAL_PRESSURE)
#define PACKETMODE	PK_BUTTONS
#include "pktdef.h"
#include "Utils.h"
#include "WinApp.h"
#include "MainWnd.h"

#include "Document.h"
#include "VQColorButton.h"

// TYPES

///////////////////////////////////////////////////////////////////////////////
// Global variables
//
const wchar_t ProgramName[] = L"VisionQuest Tablet Painter";

static LOGCONTEXT	glogContext = {0};

HINSTANCE hInst;
WWindow Wnd;
SCROLLINFO si;
HDC backbuffDC = 0;
HBITMAP backbuffer = 0;

COLORREF cutomColors[16];
VQColorButton PenColorBtn( 2, 2, 25, 25 );

float maxPressure;
int penWidth = 10;

int sx, sy;

Document doc;
int xscroll, yscroll, docTop, docLeft = 0;
int marginLeft = 27;
int marginRight = 10;
int marginTop = 27;
int marginBottom = 10;

BOOL scrolling = FALSE;

//---------------------------------------------------------------------------
LRESULT CALLBACK MainWndProc(HWND hWnd, UINT Msg,
                             WPARAM wParam, LPARAM lParam);
//---------------------------------------------------------------------------

void showerror( HWND hWnd ) {
	void* lpBuffer;

	FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
		NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
		(LPTSTR) &lpBuffer, 0, NULL );
	MessageBox( hWnd, (LPCWSTR) lpBuffer, ProgramName, MB_OK|MB_ICONERROR );
}

void msgbox( HWND hWnd, LPCWSTR msg ) {
	MessageBox( hWnd, msg, ProgramName, MB_OK );
}

///////////////////////////////////////////////////////////////////////////////
// Purpose
//		Release resources we used in this example.
//
void Cleanup( void )
{
	DeleteDC( backbuffDC );
	DeleteObject( backbuffer );
	WACOM_TRACE( "Cleanup()\n" );
	UnloadWintab( );
}

void render( HDC hDC ) {
	wchar_t str[20];
	RECT r;

	GetClientRect( Wnd, &r );

	// clear backgroud
	HBRUSH hBrush = CreateSolidBrush( RGB( 225, 225, 225 ) );
	FillRect( backbuffDC, &r, hBrush );
	DeleteObject(hBrush);

	// render document
	doc.Render( backbuffDC, docLeft-xscroll, docTop-yscroll );
	
	// render UI
	PenColorBtn.draw( backbuffDC );
	
	COLORREF pc = doc.getPenColorRef();

	int x = sx - docLeft + xscroll;
	int y = sy - docTop + yscroll;

	if( x>=0 && y>=0 && x<doc.getWidth() && y<doc.getHeight() ) {
		HPEN hPen = CreatePen( PS_SOLID, 1, pc );
		HPEN op = (HPEN)SelectObject( backbuffDC, hPen );
		if( penWidth<10 ) {
			MoveToEx( backbuffDC, sx-7, sy, 0 );
			LineTo( backbuffDC, sx+7, sy );
			MoveToEx( backbuffDC, sx, sy-7, 0 );
			LineTo( backbuffDC, sx, sy+7 );
		}
		else {
			int p2 = penWidth/2;
			Ellipse( backbuffDC, sx-p2, sy-p2, sx+p2, sy+p2 );
		}
		SelectObject( backbuffDC, op );
		DeleteObject( hPen );

		swprintf( str, L"x: %d, y: %d", x, y );
		TextOut( backbuffDC, 52, 2, str, wcslen( str ) );
	}
	
	BitBlt( hDC, 0, 0, r.right-r.left, r.bottom-r.top, backbuffDC, 0, 0, SRCCOPY );
}

///////////////////////////////////////////////////////////////////////////////
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst,
				   LPSTR lpCmdLine, int nCmdShow)
{
	MSG   Msg;
	LPCTSTR ClsName = L"Win32OOP";
	LPCTSTR WndName = ProgramName;
	AXIS pres;

	// Initialize the application class
	WApplication WinApp;

	hInst = hInstance;
	WinApp.Create(hInst, ClsName, MainWndProc);
	WinApp.Register();
	
	// WinTab
	if ( !LoadWintab( ) )
	{
		ShowError( L"Wintab not available" );
		return FALSE;
	}

	// check if WinTab available.
	if (!gpWTInfoW( WTI_DEVICES, DVC_NPRESSURE, &pres )) 
	{
		ShowError( L"WinTab Services Not Available." );
		return FALSE;
	}
	maxPressure = (float)pres.axMax;

	// Create the main window
	Wnd.Create( hInstance, ClsName, WndName );
	
	// Display the main winow
	Wnd.Show();

	// Process the main window's messages
	while( GetMessage(&Msg, NULL, 0, 0) )
	{
		TranslateMessage(&Msg);
		DispatchMessage(&Msg);

		HDC hDC = GetDC( Wnd );
		render( hDC );
		ReleaseDC( Wnd, hDC );
	}
	
	// Return Wintab resources.
	Cleanup();

	return 0;
}

///////////////////////////////////////////////////////////////////////////////
HCTX static NEAR TabletInit(HWND hWnd)
{
	HCTX hctx = NULL;
	UINT wDevice = 0;
	UINT wExtX = 0;
	UINT wExtY = 0;
	UINT wWTInfoRetVal = 0;
	AXIS TabletX = {0};
	AXIS TabletY = {0};

	// Set option to move system cursor before getting default system context.
	glogContext.lcOptions |= CXO_SYSTEM;

	// Open default system context so that we can get tablet data
	// in screen coordinates (not tablet coordinates).
	wWTInfoRetVal = gpWTInfoW(WTI_DEFSYSCTX, 0, &glogContext);
	WACOM_ASSERT( wWTInfoRetVal == sizeof( LOGCONTEXT ) );

	WACOM_ASSERT( glogContext.lcOptions & CXO_SYSTEM );

	// modify the digitizing region
	wsprintf(glogContext.lcName, L"PrsTest Digitizing %x", hInst);

	// We process WT_PACKET (CXO_MESSAGES) messages.
	glogContext.lcOptions |= CXO_MESSAGES;

	// What data items we want to be included in the tablet packets
	glogContext.lcPktData = PACKETDATA;

	// Which packet items should show change in value since the last
	// packet (referred to as 'relative' data) and which items
	// should be 'absolute'.
	glogContext.lcPktMode = PACKETMODE;

	// This bitfield determines whether or not this context will receive
	// a packet when a value for each packet field changes.  This is not
	// supported by the Intuos Wintab.  Your context will always receive
	// packets, even if there has been no change in the data.
	glogContext.lcMoveMask = PACKETDATA;

	// Which buttons events will be handled by this context.  lcBtnMask
	// is a bitfield with one bit per button.
	glogContext.lcBtnUpMask = glogContext.lcBtnDnMask;

	// Set the entire tablet as active
	wWTInfoRetVal = gpWTInfoW( WTI_DEVICES + 0, DVC_X, &TabletX );
	WACOM_ASSERT( wWTInfoRetVal == sizeof( AXIS ) );

	wWTInfoRetVal = gpWTInfoW( WTI_DEVICES, DVC_Y, &TabletY );
	WACOM_ASSERT( wWTInfoRetVal == sizeof( AXIS ) );

	glogContext.lcInOrgX = 0;
	glogContext.lcInOrgY = 0;
	glogContext.lcInExtX = TabletX.axMax;
	glogContext.lcInExtY = TabletY.axMax;

	// Guarantee the output coordinate space to be in screen coordinates.  
	glogContext.lcOutOrgX = GetSystemMetrics( SM_XVIRTUALSCREEN );
	glogContext.lcOutOrgY = GetSystemMetrics( SM_YVIRTUALSCREEN );
	glogContext.lcOutExtX = GetSystemMetrics( SM_CXVIRTUALSCREEN ); //SM_CXSCREEN );

	// In Wintab, the tablet origin is lower left.  Move origin to upper left
	// so that it coincides with screen origin.
	glogContext.lcOutExtY = -GetSystemMetrics( SM_CYVIRTUALSCREEN );	//SM_CYSCREEN );

	// Leave the system origin and extents as received:
	// lcSysOrgX, lcSysOrgY, lcSysExtX, lcSysExtY

	// open the region
	// The Wintab spec says we must open the context disabled if we are 
	// using cursor masks.  
	hctx = gpWTOpenW( hWnd, &glogContext, FALSE );

	WACOM_TRACE("HCTX: %i\n", hctx);

	return hctx;

}

void resetDocument( int w, int h ) {
	int totalDocHeight = (marginTop+marginBottom)+doc.getHeight();
	int totalDocWidth = (marginLeft+marginRight)+doc.getWidth();

	int clientWidth = w;
	int clientHeight = h;

	docTop = marginTop;
	docLeft = marginLeft;

	// reposition scroll bars
	
    // Set the horizontal scrolling range and page size
    si.cbSize = sizeof(si);
    si.fMask  = SIF_RANGE | SIF_PAGE | SIF_POS;
    si.nMin   = 0;
    si.nMax   = totalDocWidth;
    si.nPage  = clientWidth;
	si.nPos   = xscroll = min( xscroll, totalDocWidth-w );
    SetScrollInfo( Wnd, SB_HORZ, &si, TRUE );

    // Set the vertical scrolling range and page size
    si.cbSize = sizeof(si);
    si.fMask  = SIF_RANGE | SIF_PAGE | SIF_POS;
    si.nMin   = 0;
    si.nMax   = totalDocHeight;
    si.nPage  = clientHeight;
	si.nPos   = yscroll = min( yscroll, totalDocHeight-h );
    SetScrollInfo( Wnd, SB_VERT, &si, TRUE );
}

void chooseColor() {
	CHOOSECOLOR cc;
	cc.lStructSize = sizeof(cc);
	cc.hwndOwner = Wnd;
	cc.rgbResult = doc.getPenColorRef();
	cc.lpCustColors = cutomColors;
	cc.Flags = CC_RGBINIT | CC_FULLOPEN;

	if (ChooseColor( &cc )) {
		doc.setPenColor( cc.rgbResult );
	}
}
//---------------------------------------------------------------------------
LRESULT CALLBACK MainWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    HDC hDC;
	static HCTX hCtx = NULL;
	static POINT ptOld, ptNew;
	static float prsOld, prsNew;
	static RECT rcClient;
	PACKET pkt;
	BOOL fHandled = TRUE;
	LRESULT lResult = 0L;
	PAINTSTRUCT ps;

	static BOOL IsDrawing = FALSE;
	
	switch(Msg)
	{
		case WM_CREATE: {
				//HWND hWndToolbar = Wnd.CreateToolbar( hWnd );
			
				hCtx = TabletInit(hWnd);
				if (!hCtx) 
				{
					ShowError(L"Could Not Open Tablet Context.");
					SendMessage(hWnd, WM_DESTROY, 0, 0L);
				}
				//ShowCursor( FALSE );

				doc.Create( 1600, 1200 );
				doc.setPenColor( PenColorBtn.getColor() );
				doc.FloodFill( RGB( 255, 255, 255 ) );
			}
			return 1;
		case WM_HSCROLL:
			scrolling = TRUE;
			// Get all the vertial scroll bar information.
			si.cbSize = sizeof (si);
			si.fMask  = SIF_ALL;

			// Save the position for comparison later on.
			GetScrollInfo (hWnd, SB_HORZ, &si);
			xscroll = si.nPos;
			switch (LOWORD (wParam))
			{
			// User clicked the left arrow.
			case SB_LINELEFT: 
				si.nPos -= 1;
				break;
              
			// User clicked the right arrow.
			case SB_LINERIGHT: 
				si.nPos += 1;
				break;
              
			// User clicked the scroll bar shaft left of the scroll box.
			case SB_PAGELEFT:
				si.nPos -= si.nPage;
				break;
              
			// User clicked the scroll bar shaft right of the scroll box.
			case SB_PAGERIGHT:
				si.nPos += si.nPage;
				break;
              
			// User dragged the scroll box.
			case SB_THUMBTRACK: 
				si.nPos = si.nTrackPos;
				break;
              
			default :
				break;
			}

			// Set the position and then retrieve it.  Due to adjustments
			// by Windows it may not be the same as the value set.
			si.fMask = SIF_POS;
			SetScrollInfo (hWnd, SB_HORZ, &si, FALSE);
			GetScrollInfo (hWnd, SB_HORZ, &si);
         
			// If the position has changed, scroll the window.
			if (si.nPos != xscroll)
			{
				//ScrollWindow(hWnd, (xscroll - si.nPos), 0, NULL, NULL);
				//SendMessage( Wnd.GetToolbarHWND(), TB_AUTOSIZE, 0, 0 );
				//UpdateWindow (hWnd);
			}
			
			InvalidateRect( hWnd, 0, FALSE );
			return 1;
        
		case WM_VSCROLL:
			scrolling = TRUE;
			// Get all the vertial scroll bar information.
			si.cbSize = sizeof (si);
			si.fMask  = SIF_ALL;
			GetScrollInfo (hWnd, SB_VERT, &si);
			
			// Save the position for comparison later on.
			yscroll = si.nPos;
			switch (LOWORD (wParam))
			{
			// User clicked the HOME keyboard key.
			case SB_TOP:
				si.nPos = si.nMin;
				break;
              
			// User clicked the END keyboard key.
			case SB_BOTTOM:
				si.nPos = si.nMax;
				break;
              
			// User clicked the top arrow.
			case SB_LINEUP:
				si.nPos -= 1;
				break;
              
			// User clicked the bottom arrow.
			case SB_LINEDOWN:
				si.nPos += 1;
				break;
              
			// User clicked the scroll bar shaft above the scroll box.
			case SB_PAGEUP:
				si.nPos -= si.nPage;
				break;
              
			// User clicked the scroll bar shaft below the scroll box.
			case SB_PAGEDOWN:
				si.nPos += si.nPage;
				break;
              
			// User dragged the scroll box.
			case SB_THUMBTRACK:
				si.nPos = si.nTrackPos;
				break;
              
			default:
				break; 
			}
			
			// Set the position and then retrieve it.  Due to adjustments
			// by Windows it may not be the same as the value set.
			si.fMask = SIF_POS;
			SetScrollInfo (hWnd, SB_VERT, &si, FALSE);
			GetScrollInfo (hWnd, SB_VERT, &si);

			// If the position has changed, scroll window and update it.
			if (si.nPos != yscroll)
			{                    
				//ScrollWindow(hWnd, 0, (yscroll - si.nPos), NULL, NULL);
				//SendMessage( Wnd.GetToolbarHWND(), TB_AUTOSIZE, 0, 0 );
				//UpdateWindow (hWnd);
			}
			//wchar_t str[50];
			//swprintf( str, L"yscroll: %d\n", yscroll );
			//OutputDebugString( str );
			InvalidateRect( hWnd, 0, FALSE );
			return 1;
		case WM_PAINT:
			hDC = BeginPaint( hWnd, &ps );
			render( hDC );
			EndPaint( hWnd, &ps );
			return 1;
		//case WM_MOVE:
		case WM_SIZE:
		{
			hDC = GetDC( hWnd );
			DeleteDC( backbuffDC );
			backbuffDC = CreateCompatibleDC(hDC);
			DeleteObject( backbuffer );
			backbuffer = CreateCompatibleBitmap( hDC, LOWORD( lParam ), HIWORD( lParam ) );
			SelectObject( backbuffDC, backbuffer );
				//hDC = GetDC(hWnd);
			SelectObject( backbuffDC, GetStockBrush( NULL_BRUSH ) );
				//ReleaseDC( hWnd, hDC );

			resetDocument( LOWORD( lParam ), HIWORD( lParam ) );
		
			return 1;
		}
		case WM_ERASEBKGND:
			return 1;
		case WM_MOUSEMOVE: {
				sx = GET_X_LPARAM( lParam );
				sy = GET_Y_LPARAM( lParam );
				scrolling = FALSE;
			}
			return 1;
		case WT_PACKET:
			if (gpWTPacket((HCTX)lParam, wParam, &pkt)) 
			{
				static int curbutton = -1;
				ptNew.x = pkt.pkX - docLeft + xscroll;
				ptNew.y = pkt.pkY - docTop + yscroll;
				ScreenToClient(hWnd, &ptNew);

				//if( ptOld.x == ptNew.x && ptOld.y == ptNew.y )
				//	break;

				prsNew = (pkt.pkNormalPressure / maxPressure * penWidth);
				
				if (HIWORD(pkt.pkButtons)==TBN_DOWN ) {

					switch( LOWORD( pkt.pkButtons ) ) {
					case 0:
						if( curbutton==-1 ) {
							IsDrawing = TRUE;
							doc.BeginStroke( ptNew.x, ptNew.y );
						}
						break;
					default:
						;
					}
					if( curbutton == -1 )
						curbutton = LOWORD( pkt.pkButtons );
					
					prsOld = 0;
				}
				else if ( HIWORD(pkt.pkButtons)==TBN_UP ) {
					IsDrawing = FALSE;
					doc.EndStroke();
					if( curbutton == LOWORD( pkt.pkButtons ) )
						curbutton = -1;
				}
				else {
					if (IsDrawing /*&& !(ptOld.x==ptNew.x && ptOld.y==ptNew.y && prsOld==prsNew)*/) {
						if( scrolling ) {
							IsDrawing = FALSE;
							doc.DiscardLine();
						}
						else
							doc.DrawLine( ptOld.x, ptOld.y, ptNew.x, ptNew.y, prsOld, prsNew );
						InvalidateRect( hWnd, 0, FALSE );
					}
					prsOld = prsNew;
					ptOld.x = ptNew.x;
					ptOld.y = ptNew.y;
				}
			}
			return 1;

		case WM_ACTIVATE:
			if (GET_WM_ACTIVATE_STATE(wParam, lParam))
				InvalidateRect(hWnd, NULL, TRUE);

			/* if switching in the middle, disable the region */
			if (hCtx) {
				gpWTEnable(hCtx, GET_WM_ACTIVATE_STATE(wParam, lParam));
				if (hCtx && GET_WM_ACTIVATE_STATE(wParam, lParam))
					gpWTOverlap(hCtx, TRUE);
			}
			return 1;

	case WM_DESTROY:
		if (hCtx)
			gpWTClose(hCtx);
		PostQuitMessage(WM_QUIT);
		return 1;
	case WM_LBUTTONDOWN:
		if( PenColorBtn.intersect( sx, sy ) ) {
			PenColorBtn.action( hWnd );
			doc.setPenColor( PenColorBtn.getColor() );
		}
		return 1;
	//case WM_LBUTTONUP:
	//	scrolling = FALSE;
	//	return 1;
	case WM_KEYDOWN:
		switch( wParam ) {
		case 'C':
			chooseColor();
			break;
		case 'S':
			{
				POINT p;
				GetCursorPos( &p );
				HDC hdcSrc = GetDC(0);
				doc.setPenColor( GetPixel( hdcSrc, p.x, p.y ) );
				ReleaseDC( 0, hdcSrc );
			}
			break;
		case VK_OEM_4:
			if( penWidth--<1 ) penWidth = 1;
			break;
		case VK_OEM_6:
			if( penWidth++>100 ) penWidth = 100;
			break;
		}
		return 1;
	}

	return DefWindowProc(hWnd, Msg, wParam, lParam);
}
//---------------------------------------------------------------------------
